﻿using System;
using CoreTypes;
using System.Xml;
using System.Net;
using System.Linq;
using WODXMPPCOMLib;
using System.Custom;
using System.Drawing;
using System.Xml.Linq;
using System.Custom.Dns;
using System.Net.Sockets;
using System.Custom.Window;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;

namespace XmppServiceTransport
{
   public class CommunicationServiceObject : CommunicationServiceObjectBase
   {
      #region Default Network Settings

      public const string DefaultNetworkSettings =

@"<XmppServiceTransport>

      <Service Name='Jabber'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginAlias='Jabber ID'
         HomePage='http://www.jabber.org/'
         PersonalUrl='http://www.jabber.org/'
         RegisterUrl='http://register.jabber.org/'
         PasswordUrl='http://www.jabber.org/lost-password/'
         Description='The jabber.org IM service is a key node on the world’s largest network of open instant messaging services.'>
         <Configuration Name='Jabber.org'
                        Host='jabber.org'
                        UserDefined='false'
                        ReadOnly='true'>
            <Supports Actions='Password'
                      Features='BlockList AddGroup RemoveGroup AddContact'                      
                      GettingOf='Nickname FullName FirstName LastName Email Country City'
                      SettingOf='Nickname FullName FirstName LastName Email Country City'
                      SearchBy='' />
         </Configuration>
      </Service>

      <Service Name='GTalk'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginAlias='Jabber ID'
         LoginCaption='Gmail'
         HomePage='http://www.gmail.com/'
         PersonalUrl='https://www.google.com/accounts/ManageAccount'
         RegisterUrl='https://www.google.com/accounts/NewAccount'
         PasswordUrl='https://www.google.com/accounts/ForgotPasswd'
         Description='Google Jabber service. Talk with millions of users who have a Gmail account.'>
         <Configuration Host='talk.google.com'
                        UserDefined='false'
                        ReadOnly='true'>
             <Supports Features='BlockList AddGroup RemoveGroup AddContact'
                       GettingOf='Nickname FullName Userpic'
                       SettingOf='Userpic'
                       Actions=''
                       SearchBy='' />
        </Configuration>
      </Service>

      <Service Name='VK'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginCaption='ID of the page or login'
         LoginAlias='Jabber ID'
         LoginSample='id6883851@vk.com or wasyapypkin@vk.com'
         HomePage='http://www.vk.com/'
         PersonalUrl='http://www.vk.com/'
         RegisterUrl='http://www.vk.com/'
         PasswordUrl='http://vk.com/login.php?op=forgot'
         Description='Jabber server of the vk.com social network. Originated from Russian network vkontakte.ru'>
         <Configuration Host='vkmessenger.com'
                        UserDefined='false'
                        ReadOnly='true'>
            <Supports Features='BlockList AddGroup RemoveGroup AddContact'
                      Actions='Password'
                      GettingOf='Userpic Nickname Email'
                      SettingOf='' 
                      SearchBy='' />
        </Configuration>
      </Service>

      <Service Name='LJ Talk'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginAlias='Jabber ID'
         LoginCaption='LiveJournal'
         HomePage='http://www.livejournal.com/'
         PersonalUrl='https://www.livejournal.com/login.bml'
         RegisterUrl='https://www.livejournal.com/create.bml'
         PasswordUrl='http://www.livejournal.com/lostinfo.bml'
         Description='Post to your journal by sending message to Frank :) IM with your LJ friends!'>
         <Configuration Host='livejournal.com'
                        UserDefined='false'
                        ReadOnly='true'>
             <Supports Features='BlockList AddGroup RemoveGroup AddContact'
                       Actions=''
                       SettingOf='Userpic'
                       GettingOf='Userpic Nickname Userpic FullName Birthday PersonalUrl City Country' />
        </Configuration>
      </Service>

      <Service Name='VKontakte'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginCaption='ID страницы или логин'
         LoginAlias='Jabber ID'
         LoginSample='id6883851@vk.com или wasyapypkin@vk.com'
         Culture='RU'
         HomePage='http://www.vkontakte.ru/'
         PersonalUrl='http://www.vkontakte.ru/'
         RegisterUrl='http://www.vkontakte.ru/'
         PasswordUrl='http://vkontakte.ru/login.php?op=forgot'
         Description='Jabber сервер самой популярной в России социальной сети для молодежи VKontakte.ru'>
         <Configuration Host='vkmessenger.com'
                        UserDefined='false'
                        ReadOnly='true'>
            <Supports Features='BlockList AddGroup RemoveGroup AddContact'
                      Actions='Password'
                      GettingOf='Userpic Nickname Email'
                      SettingOf='' 
                      SearchBy='' />
        </Configuration>
      </Service>

      <Service Name='Jabber.ru'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginAlias='Jabber ID'
         Culture='RU'
         HomePage='http://www.jabber.ru/'
         PersonalUrl='http://www.jabber.ru/'
         RegisterUrl='http://reg.jabber.ru/xreg/'
         PasswordUrl='http://reg.jabber.ru/xremind/'
         Description='Один из самых популярных Jabber серверов в России.'>
         <Configuration Host='jabber.ru'
                        UserDefined='false'
                        ReadOnly='true'>
            <Supports Features='BlockList AddGroup RemoveGroup AddContact FindPerson PeopleSearch SearchByMask'
                      Actions='Password'
                      GettingOf='Userpic Nickname FullName FirstName LastName Email Country City'
                      SettingOf='Userpic Nickname FullName FirstName LastName Email Country City' 
                      SearchBy='Id Email FirstName FullName MiddleName LastName Nickname Birthday Country City Organization Department' />
        </Configuration>
      </Service>

      <Service Name='Local Network'
         ProtocolName='XMPP'
         ProtocolVersion='1'
         LoginPrefix='JID'
         LoginAlias='Jabber ID'
         Description='Local network XMPP messaging service.'>
      </Service>

      <Service Name='MSN'
         ProtocolName='MSNP'
         ProtocolVersion='15'
         LoginPrefix='LiveId'
         LoginAlias='Live ID'
         HomePage='http://www.live.com/'
         PersonalUrl='https://account.live.com/'
         RegisterUrl='https://signup.live.com/'
         PasswordUrl='https://login.live.com/'
         Description='Microsoft Live messaging service.'>
      </Service>

  </XmppServiceTransport>";

      #endregion

      #region CommunicationServiceObject Data

      static bool _debugLog;// = true;

      static int FMessageSentTimeout = 1500;
      static int FMessageAttemtsTimeout = 4000;
      static XmppDriverSettings FDriverSettings;
      static int[] FNotLoggingErrors = { 0, 501, 503 };
      internal static IBaseCommunicator _Communicator;
      static XmppServiceConfig FDefaultLocalNetworkConfig;

      List<XMPPServiceEntry> FXmppServices = new List<XMPPServiceEntry>();
      Dictionary<Account, wodXMPPComClass> FXmppSessions = new Dictionary<Account, wodXMPPComClass>();

      List<Contact> FSubscriptionRequestedList = new List<Contact>();
      ContactListUpdateDenyObject FContactListUpdateDenyObject = new ContactListUpdateDenyObject();

      class ContactListUpdateDenyObject
      {
         static internal int DefaultDenyTimeout = 2;
         static internal int RepairAuthorisationTimeout = 10;

         internal int DenyTimeout;
         internal int ContactsCount;
         internal DateTime AccessTime;

         internal void MarkAccess(int contactsCount)
         {
            AccessTime = DateTime.Now;
            ContactsCount = contactsCount;
            DenyTimeout = DefaultDenyTimeout;
         }
         internal void MarkAccess(int contactsCount, int denySeconds)
         {
            AccessTime = DateTime.Now;
            DenyTimeout = denySeconds;
            ContactsCount = contactsCount;
         }

         internal void AllowUpdate()
         {
            DenyTimeout = -1;
            ContactsCount = 0;
            AccessTime = DateTime.Now;
         }
         internal bool UpdateAllowed(int contactsCount)
         {
            return ContactsCount >= 0 && ContactsCount != contactsCount || (DateTime.Now - AccessTime).TotalSeconds > DenyTimeout;
         }
      }

      #endregion

      #region CommunicationServiceObject Initializers

      public CommunicationServiceObject(string assemblyName, IBaseCommunicator owner)
         : base(assemblyName, owner)
      {
         _Communicator = owner;
         FDriverErrorPrefix = "XMPP driver error: ";
      }

      #endregion

      #region CommunicationServiceObject Members

      private bool IsValidJID(string jid)
      {
         return !string.IsNullOrEmpty(jid);
      }
      private Account GetAccount(User user)
      {
         return user is Account ? user as Account :
                user is Contact ? (user as Contact).Owner : FCommunicator.CurrentAccount;
      }
      internal static string _JID(string jid)
      {
         return jid != null ? jid.TrimEnd('\0').SubstrBefore("/").Replace("\\20", " ") : "";
      }
      private void UpdateSession(Account account)
      {
         wodXMPPComClass session = null;

         FXmppSessions.TryGetValue(account, out session);

         XmppServiceConfig config = (XmppServiceConfig)account.ServiceConfig;

         session.Port = config.Port;
         session.Security = config.Security;
         session.Resource = config.Resource;
         session.Priority = config.Priority;
         session.Password = account.Password;
         if (config.Host == "") config.Host = null;
         session.Authentication = config.Authentication;
         session.Login = !account.Login.Contains('@') && account.IsGtalk() ?
                          account.Login + "@gmail.com" : account.Login;

         SetProxySettings(session);

         if (_debugLog) session.DebugFile = "wodXMPP." + FCommunicator.InstanceNumber + ".log";
      }
      private string XmppToStdField(string fieldName)
      {
         if (string.IsNullOrEmpty(fieldName)) return null;

         try
         {
            XmppServiceFields field = (XmppServiceFields)Enum.Parse(typeof(XmppServiceFields), fieldName.ToLower());

            switch (field)
            {
               case XmppServiceFields.search: return "Query";
               case XmppServiceFields.jid: return ServiceFields.Id.ToString();
               case XmppServiceFields.name: return ServiceFields.Name.ToString();
               case XmppServiceFields.user: return ServiceFields.Username.ToString();
               case XmppServiceFields.username: return ServiceFields.Username.ToString();
               case XmppServiceFields.fn: return ServiceFields.FullName.ToString();
               case XmppServiceFields.first: return ServiceFields.FirstName.ToString();
               case XmppServiceFields.middle: return ServiceFields.MiddleName.ToString();
               case XmppServiceFields.last: return ServiceFields.LastName.ToString();
               case XmppServiceFields.nick: return ServiceFields.Nickname.ToString();
               case XmppServiceFields.nickname: return ServiceFields.Nickname.ToString();
               case XmppServiceFields.bday: return ServiceFields.Birthday.ToString();
               case XmppServiceFields.ctry: return ServiceFields.Country.ToString();
               case XmppServiceFields.locality: return ServiceFields.City.ToString();
               case XmppServiceFields.email: return ServiceFields.Email.ToString();
               case XmppServiceFields.orgname: return ServiceFields.Organization.ToString();
               case XmppServiceFields.orgunit: return ServiceFields.Department.ToString();
            }
            return null;
         }
         catch
         {
            return null;
         }
      }
      private bool SetProxySettings(wodXMPPComClass s)
      {
         if (string.IsNullOrEmpty(FCommunicator.ProxyType)) return false;

         if (FCommunicator.ProxyType.ToLower() == "none")
         {
            s.ProxyType = ProxyTypes.ProxyNone;
         }
         else if (FCommunicator.ProxyType.ToLower() == "socks")
         {
            s.ProxyType = ProxyTypes.ProxySocks5;
         }
         else if (FCommunicator.ProxyType.ToLower() == "socks4")
         {
            s.ProxyType = ProxyTypes.ProxySocks4;
         }
         else if (FCommunicator.ProxyType.ToLower() == "socks4a")
         {
            s.ProxyType = ProxyTypes.ProxySocks4a;
         }
         else if (FCommunicator.ProxyType.ToLower() == "socks5")
         {
            s.ProxyType = ProxyTypes.ProxySocks5;
         }
         else if (FCommunicator.ProxyType.ToLower().Contains("http"))
         {
            s.ProxyType = ProxyTypes.ProxyWEBStandard;
         }
         else
         {
            return false;
         }

         s.ProxyHostname = FCommunicator.ProxyHost;
         s.ProxyLogin = FCommunicator.ProxyUsername;
         s.ProxyPassword = FCommunicator.ProxyPassword;

         int port = 0;
         int.TryParse(FCommunicator.ProxyPort, out port);
         s.ProxyPort = port;

         return true;
      }
      private string GetLogonDomain(NetworkOperation nwo)
      {
         return _JID((nwo.Params["Session"] as wodXMPPComClass).Login).SubstrAfterLast("@");
      }
      private void LookupLogonHost(NetworkOperation nwo)
      {
         OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.HostLookup, null));

         BackgroundWorker worker = new BackgroundWorker();

         string address = "";
         string domain = GetLogonDomain(nwo);
         string hostName = nwo.Account.ServiceConfig.Host;

         worker.DoWork += (o, e) =>
         {
            if (string.IsNullOrEmpty(hostName))
            {
               if (string.IsNullOrEmpty(domain)) domain = (nwo.Account.ServiceConfig as XmppServiceConfig).Domain;

               DnsRequest dnsReq = new DnsRequest("_xmpp-client._tcp." + domain);
               DnsResponse dnsResp = dnsReq.GetResponse(DnsRecordType.SRV);

               if (dnsResp.SRVRecords.Length > 0)
               {
                  hostName = nwo.Account.ServiceConfig.Host = dnsResp.SRVRecords[0].NameNext;
                  if (dnsResp.SRVRecords[0].Port > 0) nwo.Account.ServiceConfig.Port = dnsResp.SRVRecords[0].Port;
               }
            }

            IEnumerable<IPAddress> addrs = Dns.GetHostAddresses(hostName).Where(ip =>
                                           ip.AddressFamily == AddressFamily.InterNetwork);
            Exception error = null;

            if (addrs.Count() > 0)
            {
               Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

               foreach (IPAddress ip in addrs)
               {
                  try
                  {
                     socket.Connect(ip, nwo.Account.ServiceConfig.Port);
                     socket.Close(0);
                     address = ip.ToString();
                     break;
                  }
                  catch (Exception err)
                  {
                     error = err;
                  }
               }
            }
            if (address == "")
            {
               if (error == null)
               {
                  throw new InvalidOperationException("Host address not found.");
               }
               else
               {
                  throw error;
               }
            }
         };
         worker.RunWorkerCompleted += (o, e) =>
         {
            wodXMPPComClass session = nwo.Params["Session"] as wodXMPPComClass;

            try
            {
               if (IsOperationValid(nwo))
               {
                  if (e.Error != null)
                  {
                     OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Faulted, GetOperationFault(ErrorReasonCode.ServiceFailure, e.Error)));
                     RemoveOperation(nwo);
                  }
                  else
                  {
                     nwo.Params["Address"] = address;
                     session.Port = nwo.Account.ServiceConfig.Port;
                     if (((Status)nwo.Params["Status"]).Value == StatusCodes.Phantom) session.AutoVisible = false;

                     OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));

                     session.Connect(address);

                     OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));
                  }
                  worker.Dispose();
               }
            }
            catch
            {
               OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(session.LastError, session.LastErrorText, "")));
               RemoveOperation(nwo);
               worker.Dispose();
            }
         };
         worker.RunWorkerAsync();
      }
      private static string EvalXmppLogin(Account account)
      {
         return (account.Login + "@" + account.ServiceConfig.Host + "/" +
                  (account.ServiceConfig as XmppServiceConfig).Resource).ToLower();
      }
      private Account GetAccountBySession(wodXMPPComClass s)
      {
         return FXmppSessions.ContainsValue(s) ? FXmppSessions.SingleOrDefault(pair => pair.Value == s).Key : null;
      }
      private void UpdateContactList(wodXMPPComClass session)
      {
         if (!FContactListUpdateDenyObject.UpdateAllowed(session.Contacts.Count)) return;

         //CoreTypes.EventLog.AddRecord("UpdateContactList", true);

         Account account = GetAccountBySession(session);

         NetworkOperation nwo = GetOperationByName(account.Login + ".LOGON");

         if (nwo == null) nwo = CreateOperation(account.Login + ".CONTACTLIST", true, account, 0, null);

         XmlDocument xmlContacts = new XmlDocument();

         xmlContacts.LoadXml("<Contacts />");

         Collection<string> groupNames = new Collection<string>();

         foreach (XMPPContact xmppContact in session.Contacts)
         {
            if (_JID(xmppContact.JID) == _JID(session.Login) || !TextSvc.IsReadable(xmppContact.Group)) continue;

            foreach (string groupName in xmppContact.Group.Split('|'))
            {
               if (!groupNames.Contains(groupName))
               {
                  groupNames.Add(groupName);

                  XmlElement xGroup = xmlContacts.CreateElement("Group");
                  xGroup.SetAttribute("Name", groupName);
                  xmlContacts.DocumentElement.AppendChild(xGroup);
               }
            }
         }
         foreach (XMPPContact xmppContact in session.Contacts)
         {
            string jid = _JID(xmppContact.JID);

            if (jid == _JID(session.Login)) continue;

            Contact contact = nwo.Account.GetContact(nwo.Account.LoginPrefix + ":" + jid);
            if (contact != null && xmppContact.Nick == xmppContact.Name) xmppContact.Nick = contact.CustomName;

            XmlElement xContact = xmlContacts.CreateElement("Contact");
            xContact.SetAttribute("Login", jid);
            xContact.SetAttribute("CustomName", xmppContact.Nick);
            xContact.SetAttribute("Groups", xmppContact.Group);
            xmlContacts.DocumentElement.AppendChild(xContact);
         }

         nwo.Results["Version"] = nwo.Account.ContactsVersion + 1;
         nwo.Results["XContacts"] = xmlContacts.DocumentElement.OuterXml;

         if (!account.IsValid) account.Identifier = account.LoginPrefix + ":" + _JID(session.Login);

         if (nwo.ToString().Contains(".LOGON")) OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

         OnSyncContactsState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

         RemoveOperation(nwo);
      }
      internal XmppServiceConfig GetConfig(string configName)
      {
         ServiceConfig config = null;

         foreach (var svc in FServices)
         {
            config = svc.Configurations.FirstOrDefault(conf => conf.ToString() == configName);
            if (config != null) break;
         }

         return (XmppServiceConfig)(config == null ? FDefaultConfig : config);
      }
      private wodXMPPComClass CreateXmppSession(Account account)
      {
         try
         {
            wodXMPPComClass session = new wodXMPPComClass();

            FXmppSessions[account] = session;

            session.LicenseKey = "8FE4-7SW2-P5KJ-22UG";

            session.Error += (XMPPContact contact, XMPPChatRoom chatRoom, XMPPService service, int errorCode, string errorText) =>
            {
               session_Error(session, contact, chatRoom, service, errorCode, errorText);
            };
            session.Connected += () =>
            {
               session_Connected(session);
            };
            session.Disconnected += (int errorCode, string errorText) =>
            {
               session_Disconnected(session, errorCode, errorText);
            };
            session.ContactList += () =>
            {
               session_ContactList(session);
            };
            session.IncomingMessage += (XMPPContact contact, XMPPChatRoom chatRoom, XMPPMessage message) =>
            {
               session_IncomingMessage(session, contact, chatRoom, message);
            };
            session.ContactStatusChange += (XMPPContact contact, XMPPChatRoom chatRoom, StatusEnum newStatus, StatusEnum oldStatus) =>
            {
               session_ContactStatusChange(session, contact, chatRoom, newStatus, oldStatus);
            };
            session.ContactAuthRequest += (XMPPContact contact, ref XMPPActionsEnum action) =>
            {
               session_ContactAuthRequest(session, contact, ref action);
            };
            session.ContactSearchDone += (XMPPService service, XMPPVars items) =>
            {
               session_ContactSearchDone(session, service, items);
            };
            session.VCardDetails += (XMPPContact contact, bool partial) =>
            {
               session_VCardDetails(session, contact, partial);
            };
            session.ServiceStatusChange += (XMPPService service) =>
            {
               session_ServiceStatusChange(session, service);
            };
            session.IncomingNotification += (XMPPContact contact, ContactNotifyEnum notifyID, object data) =>
            {
               session_IncomingNotification(session, contact, notifyID, data);
            };

            return session;
         }
         catch (Exception error)
         {
            FCommunicator.ShowError("Unable to initialize XMPP driver.", error);

            return null;
         }
      }
      private void MarshalUserInfo(UserInfo info, XMPPVCard vCard)
      {
         info.Email = vCard.Email.TrimIgnoreNull();
         info.City = vCard.HomeCity.TrimIgnoreNull();
         info.PersonalUrl = vCard.URL.TrimIgnoreNull();
         info.FullName = vCard.FullName.TrimIgnoreNull();
         info.Country = vCard.HomeCountry.TrimIgnoreNull();
         info.Description = vCard.Description.TrimIgnoreNull();
         info.Phone = string.IsNullOrEmpty(vCard.HomeCellPhone.TrimIgnoreNull()) ? vCard.HomePhone.TrimIgnoreNull() : vCard.HomeCellPhone.TrimIgnoreNull();

         string birthday = NormalizeBirthdayString(vCard.BirthDay.ToShortDateString());
         if (birthday != "") info.Birthday = birthday;

         info.Work.City = vCard.WorkCity.TrimIgnoreNull();
         info.Work.Address = vCard.WorkAddress.TrimIgnoreNull();
         info.Work.Country = vCard.WorkCountry.TrimIgnoreNull();
         info.Work.Department = vCard.OrganizationUnit.TrimIgnoreNull();
         info.Work.Organization = vCard.OrganizationName.TrimIgnoreNull();
         info.Work.Phone = string.IsNullOrEmpty(vCard.WorkPhone.TrimIgnoreNull()) ? vCard.WorkCellPhone.TrimIgnoreNull() : vCard.WorkPhone.TrimIgnoreNull();

         info.Userpic = OleImageConverter.PictureDispToImage(vCard.Photo);
      }
      private static string NormalizeBirthdayString(string birthday)
      {
         DateTime? dt = UserInfo.GetBirthdayAsDate(birthday, true, true, true);

         if (dt.HasValue == false ||
             dt.Value.Year == 1901 && dt.Value.Month == 1 && dt.Value.Day == 1 ||
             dt.Value.Year == 1899 && dt.Value.Month == 12 && dt.Value.Day == 30)
         {
            birthday = "";
         }
         else
         {
            birthday = dt.Value.ToShortDateString();
         }

         return birthday;
      }
      private XMPPContact GetXmppContact(wodXMPPComClass s, string jid)
      {
         jid = _JID(jid);

         foreach (XMPPContact xmppContact in s.Contacts) if (jid == _JID(xmppContact.JID)) return xmppContact;

         return null;
      }
      private void SaveInfo(Account account, SaveAccountActions actions)
      {
         wodXMPPComClass session = GetXmppSession(account, false);

         if (session == null) return;

         NetworkOperation nwo = CreateOperation(account.Login + ".SAVEACCOUNTDATA", true, account, FTimeout, null, "Actions", actions);

         try
         {
            session.VCard.Email = account.Info.Email;
            session.VCard.NickName = account.Nickname;
            session.VCard.FullName = account.Info.FullName;
            session.VCard.HomeCity = account.Info.City;
            session.VCard.URL = account.Info.PersonalUrl;
            session.VCard.HomeCountry = account.Info.Country;
            session.VCard.Description = account.Info.Description;
            session.VCard.HomeCellPhone = account.Info.Phone;
            session.VCard.WorkCity = account.Info.Work.City;
            session.VCard.WorkAddress = account.Info.Work.Address;
            session.VCard.WorkCountry = account.Info.Work.Country;
            session.VCard.OrganizationUnit = account.Info.Work.Department;
            session.VCard.OrganizationName = account.Info.Work.Organization;
            session.VCard.WorkPhone = account.Info.Work.Phone;
            session.VCard.WorkFax = account.Info.Work.Fax;
            DateTime bd;
            if (DateTime.TryParse(account.Info.Birthday, out bd)) session.VCard.BirthDay = bd;
            Image pic = account.Info.Userpic;
            session.VCard.Photo = OleImageConverter.ImageToPictureDisp(pic);
            if (pic != null) pic.Dispose();

            session.VCard.Send();

            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, nwo, OperationState.DataSent, null));
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, nwo, OperationState.Completed, null));
         }
         catch (Exception error)
         {
            if (session.LastError > 0)
            {
               OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, nwo, OperationState.None, GetOperationFault(session.LastError, session.LastErrorText, "")));
            }
            else
            {
               OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, nwo, OperationState.None, GetOperationFault(ErrorReasonCode.Unknown, error)));
            }
         }
         RemoveOperation(nwo);
      }
      private XMPPChatRoom GetXMPPChatRoom(wodXMPPComClass s, string jid)
      {
         try
         {
            return s.ChatRooms[jid];
         }
         catch
         {
            return null;
         }
      }
      private wodXMPPComClass GetXmppSession(Account account, bool create)
      {
         wodXMPPComClass session = null;

         FXmppSessions.TryGetValue(account, out session);

         if (session == null) session = FXmppSessions.Values.FirstOrDefault(s => s.Login.ToLower() == EvalXmppLogin(account));

         return session != null ? session : (create ? CreateXmppSession(account) : null);
      }
      private Contact GetContact(wodXMPPComClass s, XMPPContact xmppContact)
      {
         Account account = GetAccountBySession(s);

         return account.GetContact(account.ServiceConfig.Service.LoginPrefix + ":" + _JID(xmppContact.JID));
      }
      private IEnumerable<NetworkOperation> FindOperation(string key, string exclude)
      {
         lock (FNetworkOperations)
         {
            if (string.IsNullOrEmpty(exclude))
               return FNetworkOperations.Where(nwo => nwo.Name.Contains(key));
            else
               return FNetworkOperations.Where(nwo => nwo.Name.Contains(key) && !nwo.Name.Contains(exclude));
         }
      }
      private IEnumerable<XMPPContact> GetXmppContacts(wodXMPPComClass s, string group)
      {
         foreach (XMPPContact contact in s.Contacts) if (contact.Group == group) yield return contact;

         yield break;
      }
      private ServiceOperationException GetOperationFault(int errno, string message, string tag)
      {
         if (errno == 0) return null;

         if (message == null) message = "";

         ErrorReasonCode reason = ErrorReasonCode.ServiceFailure;

         switch (errno)
         {
            case 501:
            {
               reason = ErrorReasonCode.ServerInternal;
               if (message == "") message = "This feature (<tag>) is not available on the server side (501)";
               break;
            }
            case 503:
            {
               reason = ErrorReasonCode.ServerInternal;
               if (message == "") message = "Service is unavailable (503)";
               break;
            }
            case 30010:
            {
               reason = ErrorReasonCode.Credentials;
               break;
            }
         }
         return new ServiceOperationException(reason, FDriverErrorPrefix + message.Replace("<tag>", tag));
      }
      private ServiceOperationException GetOperationFault(ErrorReasonCode reason, Exception error)
      {
         if (error == null) return null;

         return new ServiceOperationException(reason, FDriverErrorPrefix + error.Message, error);
      }
      private SubscriptionAction SetupSubscription(Contact contact, SubscriptionAction action, bool initByMe)
      {
         wodXMPPComClass session = GetXmppSession(contact.Owner, false);

         if (session == null || session.State != StatesEnum.Connected) return SubscriptionAction.None;

         XMPPContact xmppContact = GetXmppContact(session, contact.Login);

         if (xmppContact == null)
         {
            xmppContact = session.Contacts.Add(contact.Login);
            xmppContact.Group = contact.Group.Name;
         }

         if (action == SubscriptionAction.Auto)
         {
            if (xmppContact.Subscription == SubscriptionsEnum.SubscriptionBoth &&
               (xmppContact.Status != StatusEnum.Unsubscribed || xmppContact.Status != StatusEnum.Requested))
            {
               action = SubscriptionAction.None;
            }
            else if (xmppContact.Subscription == SubscriptionsEnum.SubscriptionNone)
            {
               if (initByMe)
               {
                  action = FSubscriptionRequestedList.Contains(contact) ||
                           contact.Owner.IsBlockedContact(contact.Identifier, PrivacyType.All) ?
                           SubscriptionAction.None : SubscriptionAction.Subscribe;
               }
               else
               {
                  action = contact.Owner.IsBlockedContact(contact.Identifier, PrivacyType.BlockMessage) ? SubscriptionAction.Deny :
                           (FSubscriptionRequestedList.Contains(contact) ? SubscriptionAction.None : SubscriptionAction.Subscribe);
               }
            }
            else
            {
               action = initByMe ? SubscriptionAction.Repair : SubscriptionAction.Subscribe;
            }
         }
         if (action == SubscriptionAction.Repair)
         {
            FContactListUpdateDenyObject.MarkAccess(-1, ContactListUpdateDenyObject.RepairAuthorisationTimeout);

            if (xmppContact.Subscription == SubscriptionsEnum.SubscriptionNone)
            {
               xmppContact.Subscribe();
               
               if (!FSubscriptionRequestedList.Contains(contact)) FSubscriptionRequestedList.Add(contact);
            }
            else
            {
               xmppContact.Unsubscribe();

               DelayedAction.Do(() =>
               {
                  xmppContact = GetXmppContact(session, contact.Login);

                  if (xmppContact != null) session.Contacts.Remove(xmppContact);

                  DelayedAction.Do(() =>
                  {
                     xmppContact = session.Contacts.Add(contact.Login);
                     xmppContact.Group = contact.Group.Name;
                     xmppContact.Subscribe();
                     if (!FSubscriptionRequestedList.Contains(contact)) FSubscriptionRequestedList.Add(contact);

                     FContactListUpdateDenyObject.AllowUpdate();
                  },
                  2000);
               },
               2000);
            }
         }
         else if (action == SubscriptionAction.Remove)
         {
            FContactListUpdateDenyObject.MarkAccess(-1);

            if (xmppContact != null)
            {
               xmppContact.Unsubscribe();

               DelayedAction.Do(() =>
               {
                  xmppContact = GetXmppContact(session, contact.Login);

                  if (xmppContact != null) session.Contacts.Remove(xmppContact);
               },
               1000);
            }
         }
         else if (action == SubscriptionAction.Subscribe)
         {
            if (xmppContact != null) xmppContact.Subscribe();// else FCommunicator.ShowError("Contact is NULL", null);
         }
         else if (action == SubscriptionAction.Unsubscribe)
         {
            if (xmppContact != null) xmppContact.Unsubscribe();// else FCommunicator.ShowError("Contact is NULL", null);
         }
         return action;
      }

      #endregion

      #region ICommunicationServiceObject Members
      
      public override bool IsConnected(Account account)
      {
         wodXMPPComClass session = GetXmppSession(account, false);

         return session == null ? false : session.Status != StatusEnum.Offline;
      }
      public override ushort AddListener(string name, ushort port)
      {
         throw new NotSupportedException("Add Listener");
      }

      public override void Logoff(Account account)
      {
         wodXMPPComClass session = GetXmppSession(account, false);

         if (session != null) session.Disconnect();

         NetworkOperation nwo = GetOperationByName(account.Login + ".LOGON");

         if (nwo != null)
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Cancelled, null));
         }
         else
         {
            nwo = CreateOperation(account.Login + ".LOGOFF", true, account, 0, null, "Status", Status.Offline);

            OnNotifyPresenceState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));
         }

         RemoveOperation(nwo);

         FSubscriptionRequestedList.Clear();
      }
      public override void DropAccount(Account account)
      {
         throw new NotSupportedException("Drop Account");
      }
      public override void SyncContacts(Account account)
      {
         // There is no need to ask for contacts directly in the XMPP protocol.
      }
      public override void LoadAccountData(Account account)
      {
         RequestUserInfo(account, false);
      }
      public override void CreateAccount(RegistrationInfo info)
      {
         throw new NotSupportedException("Create Account");
      }
      public override void SendChatMessage(ChatMessage message)
      {
         wodXMPPComClass session = GetXmppSession(message.Sender as Account, false);

         if (session == null || session.Status == StatusEnum.Offline)
         {
            OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         string nwoName = message.Sender.Login + "->" + message.Recipient.Login + ".CHATMESSAGE";

         NetworkOperation nwo = CreateOperation(nwoName, true, (Account)message.Sender, 4 * FMessageAttemtsTimeout, null, "Message", message);

         if (nwo == null)
         {
            OnSendChatMessageState(this, new MessagingOperationEventArgs(message, GetOperationByName(nwoName), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready, "CHATMESSAGE")));
            return;
         }

         message.NoAcknowledges = true;
         message.TsLocal = DateTime.UtcNow;

         nwo.MessagingOperationCallback = OnSendChatMessageState;

         OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, nwo, OperationState.DataSending, null));

         try
         {
            FContactListUpdateDenyObject.MarkAccess(session.Contacts.Count);

            Contact contact = (Contact)message.Recipient;

            SubscriptionAction subscription = SetupSubscription(contact, SubscriptionAction.Auto, true);

            XMPPMessageClass xmppMessage = new XMPPMessageClass();
            xmppMessage.Text = message.Text;
            xmppMessage.Type = MessageTypesEnum.MsgChat;

            session.SendMessage(contact.Login, xmppMessage);

            DelayedAction.Do(() =>
            {
               if (nwo.AttemptCount == 0)
               {
                  OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, nwo, OperationState.DataSent, null));
                  RemoveOperation(nwo);
               }
            },
            subscription == SubscriptionAction.None ? FMessageSentTimeout : FMessageSentTimeout * 3);
         }
         catch (Exception error)
         {
            if (session.LastError > 0)
            {
               OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, nwo, OperationState.None, GetOperationFault(session.LastError, session.LastErrorText, "")));
            }
            else
            {
               OnSendChatMessageState(this, new MessagingOperationEventArgs(message, null, nwo, OperationState.None, GetOperationFault(ErrorReasonCode.Unknown, error)));
            }
            RemoveOperation(nwo);
         }
      }
      public override void UpdateMessage(ServiceMessage message)
      {
         throw new NotSupportedException("Update Message");
      }
      public override void Logon(Account account, Status status)
      {
         NetworkOperation nwo = GetOperationByName(account.Login + ".LOGON");

         if (nwo != null && nwo.State != OperationState.Completed && nwo.State != OperationState.Cancelled)
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(ErrorReasonCode.ExistAlready, "", "LOGON")));
            return;
         }
         else if (nwo == null)
         {
            nwo = CreateOperation(account.Login + ".LOGON", true, account, FLogonTimeout * 100, null, "Status", status);

            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Initialization, null));

            if (nwo.State == OperationState.Cancelled)
            {
               OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Cancelled, null));
               RemoveOperation(nwo);
               return;
            }
         }

         wodXMPPComClass session = GetXmppSession(account, true);

         if (session != null)
         {
            UpdateSession(account); 
         }
         else
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Faulted, GetOperationFault(ErrorReasonCode.Network, "", "Fail to load XMPP driver.")));
            RemoveOperation(nwo);
            return;
         }
         if (session.Status == StatusEnum.Online)
         {
            OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(ErrorReasonCode.LoggedInAlready, "", "")));
            RemoveOperation(nwo);
            return;
         }

         nwo.Params["Session"] = session;

         LookupLogonHost(nwo);
      }
      public override void SeekUsers(string query, IWindow sender)
      {
         wodXMPPComClass session = GetXmppSession(FCommunicator.CurrentAccount, false);

         if (session == null || session.Status == StatusEnum.Offline)
         {
            OnSeekUsersState(this, new NetworkOperationEventArgs(null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         XMPPServiceEntry userSearch = FXmppServices.FirstOrDefault(svc => svc.Session == session && svc.Identity.Category == IdentityCategoryEnum.CatDirectory && svc.Identity.Type == "user");

         if (userSearch == null)
         {
            OnSeekUsersState(this, new NetworkOperationEventArgs(null, OperationState.None, GetOperationFault(ErrorReasonCode.NotSupported, "", "User search")));
            return;
         }

         if (query == null) query = "";

         NetworkOperation nwo = CreateOperation("SEEKUSERS", true, Communicator.CurrentAccount, FTimeout, null, "Query", query, "UserSearch", userSearch);

         if (nwo == null)
         {
            OnSeekUsersState(this, new NetworkOperationEventArgs(GetOperationByName("SEEKUSERS"), OperationState.None, GetOperationFault(ErrorReasonCode.ExistAlready, "", "SEEKUSERS")));
            return;
         }

         nwo.NetworkOperationCallback = OnSeekUsersState;

         try
         {
            session.Contacts.Search(userSearch.Service, null);

            OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));
         }
         catch (Exception error)
         {
            if (session.LastError > 0)
            {
               OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(session.LastError, session.LastErrorText, "")));
            }
            else
            {
               OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(ErrorReasonCode.Unknown, error)));
            }
            RemoveOperation(nwo);
         }
      }
      public override void SendAcknowledge(ServiceMessage message)
      {
         //
      }
      public override void SendInvitation(SessionInvitation message)
      {
         wodXMPPComClass xmppSession = GetXmppSession(message.Sender as Account, false);

         foreach (User user in message.MediaSession.Invitees)
         {
            //xmppSession.SendInvitation(message.MediaSession.Gsid, user.Login, message.MediaSession.InvitationText);
         }
      }
      public override void RequestUserInfo(User user, bool shortInfo)
      {
         Account account = GetAccount(user);

         wodXMPPComClass session = GetXmppSession(account, false);

         if (session == null || session.Status == StatusEnum.Offline)
         {
            OnRequestUserInfoState(this, new NetworkOperationEventArgs(null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         string jid = user.Login;

         NetworkOperation nwo = CreateOperation(jid + ".REQUESTUSERINFO", true, account, FTimeout, null, "User", user);

         if (nwo == null)
         {
            OnRequestUserInfoState(this, new NetworkOperationEventArgs(GetOperationByName(jid + ".REQUESTUSERINFO"), OperationState.None, GetOperationFault(ErrorReasonCode.ExistAlready, "", "REQUESTUSERINFO")));
            return;
         }

         nwo.NetworkOperationCallback = OnRequestUserInfoState;

         if (user is Account)
         {
            session.VCard.Receive();
         }
         else
         {
            XMPPContact contact = GetXmppContact(session, jid);

            if (contact == null)
            {
               contact = session.Contacts.Add(jid);
               nwo.Params["$contact$"] = jid;
            }

            contact.VCard.Receive();
         }

         OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));
      }
      public override void SendInvitationReply(ServiceMessage message)
      {
         throw new NotSupportedException("Send Invitation Reply");
      }
      public override void NotifyPresence(Account account, User user, Status status)
      {
         if (status.Value == StatusCodes.Offline)
         {
            Logoff(account);
            return;
         }

         wodXMPPComClass session = GetXmppSession(account, false);

         if (session == null || session.Status == StatusEnum.Offline)
         {
            Logon(account, status);
            return;
         }

         string nwoName = account.Login + ".NOTIFYPRESENCE";

         NetworkOperation nwo = CreateOperation(nwoName, true, account, FTimeout, null, "Status", status);

         if (nwo == null)
         {
            OnNotifyPresenceState(this, new NetworkOperationEventArgs(GetOperationByName(nwoName), OperationState.None, GetOperationFault(ErrorReasonCode.ExistAlready, "", "NOTIFYPRESENCE")));
            return;
         }

         session.SetStatus(TypeMap.MarshalStatus(status.Value), status.Message);

         OnNotifyPresenceState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSent, null));

         RemoveOperation(nwo);
      }
      public override void ChangePrivateData(Account account, string password, string mail)
      {
         throw new NotSupportedException("Change Private Data");
      }
      public override void SaveAccountData(Account account, SaveAccountActions actions, params object[] data)
      {
         if (actions == SaveAccountActions.None) return;

         wodXMPPComClass session = GetXmppSession(account, false);

         if (session == null || session.Status == StatusEnum.Offline)
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, null, OperationState.Completed, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         bool handled = false;

         if ((actions & SaveAccountActions.SaveInfo) == SaveAccountActions.SaveInfo ||
             (actions & SaveAccountActions.UploadUserpic) == SaveAccountActions.UploadUserpic)
         {
            handled = true;

            SaveInfo(account, actions);
         }

         if ((actions & SaveAccountActions.AddContact) == SaveAccountActions.AddContact)
         {
            handled = true;

            // SetupSubscription will be called automatically after several sent/rcvd messages.
         }
         else if ((actions & SaveAccountActions.AddGroup) == SaveAccountActions.AddGroup)
         {
            handled = true;

            // Adding a group is nothing in XMPP, assign XMPPContact.Group is an adding the group.
         }
         else if ((actions & SaveAccountActions.RemoveContact) == SaveAccountActions.RemoveContact)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RemoveContact.ToString());

            SetupSubscription((Contact)data[0], SubscriptionAction.Remove, true);
         }
         else if ((actions & SaveAccountActions.RemoveGroup) == SaveAccountActions.RemoveGroup)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RemoveGroup.ToString());
            if (!(data[0] is ContactGroup)) throw new Exception("Invalid param type for SaveAccountData." + SaveAccountActions.RemoveGroup.ToString());

            foreach (XMPPContact contact in GetXmppContacts(session, (data[0] as ContactGroup).Name)) contact.Group = "";
         }
         else if ((actions & SaveAccountActions.MoveContact) == SaveAccountActions.MoveContact)
         {
            handled = true;

            if (data.Length < 2) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.MoveContact.ToString());
            if (!(data[1] is ContactGroup)) throw new Exception("Invalid param type for SaveAccountData." + SaveAccountActions.MoveContact.ToString());

            Contact contact = (Contact)data[0];
            XMPPContact buddy = GetXmppContact(session, contact.Login);
            if (buddy != null) buddy.Group = (data[1] as ContactGroup).Name;
         }
         else if ((actions & SaveAccountActions.RenameContactsItem) == SaveAccountActions.RenameContactsItem)
         {
            handled = true;

            if (data.Length < 1) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());

            if (data[0] is Contact)
            {
               Contact contact = (Contact)data[0];
               XMPPContact buddy = GetXmppContact(session, contact.Login);
               if (buddy != null) buddy.Name = contact.CustomName;
            }
            else if (data[0] is ContactGroup)
            {
               if (data.Length < 2) throw new Exception("Invalid params count for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());
               if (!(data[1] is string)) throw new Exception("Invalid param type for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());
               foreach (XMPPContact contact in GetXmppContacts(session, (string)data[1])) contact.Group = (data[0] as ContactGroup).Name;
            }
            else
            {
               throw new Exception("Invalid param type for SaveAccountData." + SaveAccountActions.RenameContactsItem.ToString());
            }
         }

         if (handled)
         {
            FContactListUpdateDenyObject.MarkAccess(-1, 3);
         }
         else
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(actions, null, OperationState.None, GetOperationFault(ErrorReasonCode.NotSupported, "", actions.ToString())));
         }
      }
      public override void SaveMediaSession(IMediaSession session, SaveMediaSessionActions actions, params object[] data)
      {
         wodXMPPComClass xmppSession = GetXmppSession(session.Account, false);

         if (xmppSession == null || xmppSession.Status == StatusEnum.Offline)
         {
            OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, null, OperationState.None, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         bool handled = false;

         if ((actions & SaveMediaSessionActions.Update) == SaveMediaSessionActions.Update)
         {
            try
            {
               XMPPChatRoom room = GetXMPPChatRoom(xmppSession, session.Gsid);

               if (room == null)
               {
                  handled = true;

                  string nwoName = "{" + session.Gsid + "}.SAVESESSION";

                  NetworkOperation nwo = CreateOperation(nwoName, true, session.Account, FLongTimeout, null, "Actions", actions, "Data", data);

                  if (nwo == null)
                  {
                     OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, GetOperationByName(nwoName), OperationState.None, new ServiceOperationException(ErrorReasonCode.ExistAlready)));
                  }
                  else
                  {
                     XMPPServiceEntry chatSvc = FXmppServices.FirstOrDefault(svc => svc.Session == xmppSession && svc.Identity.Category == IdentityCategoryEnum.CatConference);

                     xmppSession.ChatRooms.Add(session.Gsid, chatSvc != null ? chatSvc.Service : null);

                     xmppSession.ChatRooms.Join(session.Gsid, session.Account.Nickname, session.JoinKey, chatSvc != null ? chatSvc.Service : null);

                     OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, nwo, OperationState.DataSending, null));
                  }
               }
            }
            catch (Exception err)
            {

               throw;
            }
         }
         else
         {
            if ((actions & SaveMediaSessionActions.ChangeSubject) == SaveMediaSessionActions.ChangeSubject)
            {
            }
            if ((actions & SaveMediaSessionActions.AddParticipant) == SaveMediaSessionActions.AddParticipant)
            {
            }
         }
         if (!handled)
         {
            OnSaveMediaSessionState(this, new SaveMediaSessionEventArgs(session, actions, null, OperationState.None, new ServiceOperationException(ErrorReasonCode.NotSupported)));
         }
      }
      public override void SetPermissions(Account account, object target, PolicyRule message, PolicyRule invitation, PolicyRule visibility)
      {
         wodXMPPComClass session = GetXmppSession(account, false);

         if (session == null || session.Status == StatusEnum.Offline)
         {
            OnSaveAccountDataState(this, new SaveAccountDataEventArgs(SaveAccountActions.SaveSettings, null, OperationState.Completed, GetOperationFault(ErrorReasonCode.ConnectionRequired)));
            return;
         }

         if (target is Contact)
         {
            Contact contact = (Contact)target;
            PolicyRule oldVisibility = contact.Settings.VisibilityRule;

            if (visibility == PolicyRule.Block || visibility == PolicyRule.Deny)
            {
               SetupSubscription(contact, SubscriptionAction.Unsubscribe, true);
            }
            else if (oldVisibility == PolicyRule.Block || oldVisibility == PolicyRule.Deny)
            {
               SetupSubscription(contact, SubscriptionAction.Repair, true);
            }
         }
      }

      #endregion

      #region CommunicationServiceObjectBase

      public override bool SupportsLocalNetworks
      {
         get
         {
            return true;
         }
      }

      protected override void CreateDefaultConfig()
      {
         FDefaultConfig = new XmppServiceConfig(FServices.First(s => s.ServiceName == "Jabber"));
      }
      protected override string GetDefaultNetworkSettings()
      {
         return DefaultNetworkSettings;
      }

      public override void ShowDriverSettings(string configName)
      {
         if (FDriverSettings == null) FDriverSettings = new XmppDriverSettings(this);

         FDriverSettings.Show(configName);
      }
      public override void GoVendorPage(VendorPage page, object tag)
      {
         if (tag is XmppServiceConfig)
         {
            XmppServiceConfig config = tag as XmppServiceConfig;

            if (page == VendorPage.RegisterUser)
            {
               FCommunicator.ShowRegisterAccount(config);
            }
            else
            {
               if (config.Host.Contains("jabber.org"))
               {
                  base.GoVendorPage(page, tag);
               }
               else
               {
                  Shell.OpenLink(config.Host, "");
               }
            }
         }
         else
         {
            base.GoVendorPage(page, tag);
         }
      }
      public override ServiceConfig GetLocalNetworkConfig(bool getDefault)
      {
         XmppServiceConfig localNetworkConfig = null;
         ServiceInfo localNetworkSvc = FServices.FirstOrDefault(s => s.ServiceName == "Local Network");

         if (localNetworkSvc != null)
         {
            if (getDefault)
            {
               if (FDefaultLocalNetworkConfig == null)
               {
                  FDefaultLocalNetworkConfig = new XmppServiceConfig(localNetworkSvc);
                  FDefaultLocalNetworkConfig.Authentication = AuthenticationsEnum.AuthNTLMCurrentUser;
                  FDefaultLocalNetworkConfig.Name = Environment.UserDomainName.SubstrBefore(".");
                  AddConfig(FDefaultLocalNetworkConfig);
               }
               localNetworkConfig = FDefaultLocalNetworkConfig;
            }
            else
            {
               localNetworkConfig = (XmppServiceConfig)localNetworkSvc.Configurations.FirstOrDefault();
            }
         }
         return localNetworkConfig;
      }
      public override IRegisterWidget CreateRegisterWidget(ServiceConfig config)
      {
         RegisterWidget widget = new RegisterWidget((XmppServiceConfig)config);
         widget.Dock = DockStyle.Fill;
         return widget;
      }
      public override ServiceConfig CreateConfig(XElement xConfig, ServiceInfo info)
      {
         return new XmppServiceConfig(xConfig, info);
      }

      #endregion

      #region WodXmpp Client Handlers

      private void session_Connected(wodXMPPComClass session)
      {
         NetworkOperation nwo = GetOperationByName(GetAccountBySession(session).Login + ".LOGON");

         if (nwo == null)
         {
            session.Disconnect();
            return;
         }

         if (nwo.State == OperationState.Cancelled)
         {
            session.Disconnect();
         }
         else
         {
            FXmppServices.RemoveAll(svc => svc.Session == session);

            session.Services.Discover(true);

            OnSyncContactsState(this, new NetworkOperationEventArgs(nwo, OperationState.DataSending, null));
         }
      }
      private void session_ContactList(wodXMPPComClass session)
      {
         //CoreTypes.EventLog.AddRecord("session_ContactList", true);

         DelayedAction.Do(session, () => UpdateContactList(session), 1000, true);
      }
      private void session_ServiceStatusChange(wodXMPPComClass session, XMPPService service)
      {
         foreach (XMPPIdentity si in service.Identity)
         {
            FXmppServices.Add(new XMPPServiceEntry() { Session = session, Service = service, Identity = si });

            if (si.Type == "user" && si.Category == IdentityCategoryEnum.CatDirectory) session.Contacts.Search(service, null);
         }
      }
      private void session_Disconnected(wodXMPPComClass session, int errorCode, string errorText)
      {
         Account account = GetAccountBySession(session);
         NetworkOperation nwo = GetOperationByName(account.Login + ".LOGON");

         //FXmppSessions.Remove(account);

         if (nwo != null && errorCode > 0)
         {
            if (++nwo.AttemptCount < 1)
            {
               if (errorCode == 30010)
               {
                  session.Connect(nwo.Params["Address"]);
               }
               else
               {
                  LookupLogonHost(nwo);
               }
            }
            else
            {
               OnLogonState(this, new NetworkOperationEventArgs(nwo, OperationState.Faulted, GetOperationFault(errorCode, errorText, "")));
               RemoveOperation(nwo);
            }
         }
         else if (nwo == null)
         {
            nwo = CreateOperation(account.Login + ".LOGOFF", true, account, 0, null, "Status", Status.Offline);
            OnNotifyPresenceState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(errorCode, errorText, "")));
            RemoveOperation(nwo);
         }
      }
      private void session_VCardDetails(wodXMPPComClass session, XMPPContact xmppContact, bool partial)
      {
         if (xmppContact == null)
         {
            FContactListUpdateDenyObject.MarkAccess(session.Contacts.Count);

            string jid = GetAccountBySession(session).Login;

            NetworkOperation nwo = GetOperationByName(jid + ".REQUESTUSERINFO");

            if (nwo != null)
            {
               User user = (User)nwo.Params["User"];

               MarshalUserInfo(user.Info, session.VCard);

               if (!string.IsNullOrEmpty(session.VCard.NickName.TrimIgnoreNull()))
               {
                  user.Nickname = session.VCard.NickName.TrimIgnoreNull();
               }

               nwo.Results["User"] = user;

               OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

               RemoveOperation(nwo);
            }
         }
         else
         {
            string jid = _JID(xmppContact.JID);

            //CoreTypes.EventLog.AddRecord(jid, true);

            NetworkOperation nwo = GetOperationByName(jid + ".REQUESTUSERINFO");

            if (nwo != null)
            {
               if (nwo.Params.ContainsKey("$contact$"))
               {
                  session.Contacts.Remove((string)nwo.Params["$contact$"]);
               }

               FContactListUpdateDenyObject.MarkAccess(session.Contacts.Count);

               User user = (User)nwo.Params["User"];

               user.CustomName = xmppContact.Nick;
               
               MarshalUserInfo(user.Info, xmppContact.VCard);
               
               if (!string.IsNullOrEmpty(xmppContact.VCard.NickName.TrimIgnoreNull()))
               {
                  user.Nickname = xmppContact.VCard.NickName.TrimIgnoreNull();
               }

               nwo.Results["User"] = user;

               OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

               RemoveOperation(nwo);
            }
         }
      }
      private void session_ContactSearchDone(wodXMPPComClass session, XMPPService service, XMPPVars vars)
      {
         NetworkOperation nwo = GetOperationByName("SEEKUSERS");

         if (nwo == null)
         {
            if (vars.Type == VariableTypesEnum.VarForm)
            {
               ServiceConfig cfg = GetAccountBySession(session).ServiceConfig;

               cfg.RemoveCapabilityValue("SearchBy", null); // Clear values

               foreach (XMPPVar field in vars)
               {
                  string fieldName = XmppToStdField(field.Name);

                  if (!string.IsNullOrEmpty(fieldName)) cfg.AddCapabilityValue("SearchBy", fieldName);
               }
            }
            return;
         }

         if (vars.Type == VariableTypesEnum.VarForm)
         {
            try
            {
               string query = (string)nwo.Params["Query"];

               if (!query.Contains('=')) query = "Query=\"" + query + "\"";

               XmlAttribute xAttr = null;
               XmlDocument xQuery = new XmlDocument();
               xQuery.LoadXml("<x " + query + " />");

               IEnumerable<XMPPVar> fields = vars.Cast<XMPPVar>().Where(v => v.Type == VariableTypesEnum.VarString && !string.IsNullOrEmpty(v.Name));

               foreach (XMPPVar field in fields)
               {
                  xAttr = xQuery.DocumentElement.Attributes[XmppToStdField(field.Name)];

                  if (xAttr != null && !string.IsNullOrEmpty(xAttr.Value)) field.Value = xAttr.Value;
               }
               if (fields.Count(f => !string.IsNullOrEmpty((string)f.Value)) == 0)
               {
                  XMPPVar field = null;

                  if (fields.Count(f => f.Name == "search") == 0)
                  {
                     xAttr = xQuery.DocumentElement.Attributes["Query"];

                     if (xAttr != null && !string.IsNullOrEmpty(xAttr.Value))
                     {
                        if (xAttr.Value.Contains('@'))
                        {
                           field = fields.FirstOrDefault(f => f.Name == "email");
                        }
                        else
                        {
                           field = fields.FirstOrDefault(f => f.Name == "nick");
                           if (field == null) field = fields.FirstOrDefault(f => f.Name == "fn");
                           if (field == null) field = fields.FirstOrDefault(f => f.Name == "name");
                           if (field == null) field = fields.FirstOrDefault(f => f.Name == "last");
                           if (field == null) field = fields.FirstOrDefault(f => f.Name == "first");
                           if (field == null) field = fields.FirstOrDefault(f => f.Name == "nickname");
                           if (field == null) field = fields.FirstOrDefault(f => f.Name == "username");
                        }                        
                     }
                  }
                  if (field != null)
                  {
                     field.Value = xAttr.Value;
                  }
                  else
                  {
                     throw new InvalidOperationException("Supported search fields not found or empty.");
                  }
               }
               session.Contacts.Search(((XMPPServiceEntry)nwo.Params["UserSearch"]).Service, vars);
            }
            catch (Exception error)
            {
               if (session.LastError > 0)
               {
                  OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(session.LastError, session.LastErrorText, "")));
               }
               else
               {
                  OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(ErrorReasonCode.Unknown, error)));
               }
               RemoveOperation(nwo);
            }
         }
         else if (vars.Type == VariableTypesEnum.VarReport)
         {
            Collection<User> users = new Collection<User>();

            try
            {
               XMPPVar jidCol = vars[XmppServiceFields.jid.ToString()];

               for (int i = 0; i < jidCol.SubCount; i++)
               {
                  User user = new User(nwo.Account.LoginPrefix + ":" + jidCol.get_SubValue(i));

                  foreach (XMPPVar col in vars)
                  {
                     if (col.Name == XmppServiceFields.nick.ToString()) user.Nickname = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.fn.ToString()) user.Info.FullName = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.ctry.ToString()) user.Info.Country = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.locality.ToString()) user.Info.City = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.email.ToString()) user.Info.Email = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.orgname.ToString()) user.Info.Work.Organization = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.orgunit.ToString()) user.Info.Work.Department = col.get_SubValue(i);
                     else if (col.Name == XmppServiceFields.bday.ToString())
                     {
                        string birthday = NormalizeBirthdayString(col.get_SubValue(i));
                        if (birthday != "") user.Info.Birthday = birthday;
                     }
                  }
                  users.Add(user);
               }
            }
            finally
            {
               nwo.Results["Users"] = users;

               OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.Completed, null));

               RemoveOperation(nwo);
            }
         }
         else
         {
            OnSeekUsersState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(ErrorReasonCode.NoMeaningfulResult, "", "")));
            
            RemoveOperation(nwo);
         }
      }
      private void session_ContactAuthRequest(wodXMPPComClass session, XMPPContact xmppContact, ref XMPPActionsEnum action)
      {
         string jid = _JID(xmppContact.JID);

         ChatMessage message = new ChatMessage(ServiceActions.AUTHREQ, "", new User(jid), new User(_JID(session.Login)),
                                               GetAccountBySession(session).ServiceConfig.Service, true);

         MessagingOperationEventArgs args = new MessagingOperationEventArgs(message, null, OperationState.None, null);

         OnChatMessageReceived(this, args);

         Contact contact = message.Sender as Contact;

         if (xmppContact.Subscription == SubscriptionsEnum.SubscriptionNone)
         {
            action = args.AppliedRule == PolicyRule.Deny ||
                     args.AppliedRule == PolicyRule.Block ?
                     XMPPActionsEnum.Deny : XMPPActionsEnum.Allow;
         }
         else action = XMPPActionsEnum.Allow;

         if (action == XMPPActionsEnum.Allow)
         {
            if (SetupSubscription(contact, SubscriptionAction.Auto, false) == SubscriptionAction.Deny) action = XMPPActionsEnum.SilentDeny;
         }
      }
      private void session_IncomingNotification(wodXMPPComClass session, XMPPContact xmppContact, ContactNotifyEnum notifyID, object data)
      {
         if (notifyID == ContactNotifyEnum.UserTyping)
         {
            OnChatMessageReceived(this, new MessagingOperationEventArgs(new TypingNotification(false, (bool)data == false, new User(CommunicationServiceObject._JID(xmppContact.JID)),
                                 new User(CommunicationServiceObject._JID(session.Login)), GetAccountBySession(session).ServiceConfig.Service), null, OperationState.None, null));
         }
      }
      private void session_IncomingMessage(wodXMPPComClass session, XMPPContact xmppContact, XMPPChatRoom chatRoom, XMPPMessage message)
      {
         switch (message.Type)
         {
            case MessageTypesEnum.MsgInvite:
            {
               return;
            }
            case MessageTypesEnum.MsgChat:
            case MessageTypesEnum.MsgNormal:
            {
               //FContactListUpdateDenyObject.MarkAccess(session.Contacts.Count);

               OnChatMessageReceived(this, new MessagingOperationEventArgs(new ChatMessage(message.Text, new User(CommunicationServiceObject._JID(xmppContact.JID)),
                                    new User(CommunicationServiceObject._JID(session.Login)), GetAccountBySession(session).ServiceConfig.Service), null, OperationState.None, null));
               break;
            }
            case MessageTypesEnum.MsgError:
            {
               Contact contact = GetContact(session, xmppContact);

               if (contact == null) return;

               Account account = contact.Owner;
               string srcText = message.Text.Substr(0, 30);
               string errorText = "Message \"" + srcText + "\" was not delivered to \"" + contact.ToString() + "\"." +
                                  (message.ErrorText != "" ? "\r\nError message: " + message.ErrorText : "");

               NetworkOperation nwo = GetOperationByName(account.Login + "->" + contact.Login + ".CHATMESSAGE");

               ChatMessage srcMessage = nwo == null ? null : nwo.Params["Message"] as ChatMessage;

               if (nwo != null) nwo.Params["EnableMessageSentEvent"] = false;

               if (nwo == null || ++nwo.AttemptCount > 3)
               {
                  ServiceMessage ack = new ServiceMessage(ServiceActions.MSGACK, account.ServiceConfig.Service, 0,
                                                          new User("system@" + xmppContact.ServerName),
                                                          account, message.ErrorCode, message.ErrorText);
                  ack.TsLocal = DateTime.UtcNow;

                  if (message.ErrorCode > 0)
                  {
                     OnAcknowledgeReceived(this, new MessagingOperationEventArgs(srcMessage, ack, nwo, OperationState.Callback,
                                           GetOperationFault(message.ErrorCode, errorText, "")));
                  }
                  else
                  {
                     OnAcknowledgeReceived(this, new MessagingOperationEventArgs(srcMessage, ack, nwo, OperationState.Callback,
                                           GetOperationFault(ErrorReasonCode.DeliverFailed, errorText, "")));
                  }
                  RemoveOperation(nwo);
               }
               else
               {
                  DelayedAction.Do(() =>
                  {
                     session.SendText(contact.Login, srcMessage.Text);

                     nwo.Params["EnableMessageSentEvent"] = true;

                     DelayedAction.Do(() =>
                     {
                        if ((bool)nwo.Params["EnableMessageSentEvent"])
                        {
                           OnSendChatMessageState(this, new MessagingOperationEventArgs(srcMessage, null, nwo, OperationState.DataSent, null));
                           RemoveOperation(nwo);
                        }
                     },
                     FMessageSentTimeout);
                  },
                  FMessageAttemtsTimeout);
               }
               break;
            }
            case MessageTypesEnum.MsgGroupChat:
            break;
            case MessageTypesEnum.MsgHeadline:
            break;
            case MessageTypesEnum.MsgUnknown:
            break;
            default:
            break;
         }
      }
      private void session_Error(wodXMPPComClass session, XMPPContact xmppContact, XMPPChatRoom chatRoom, XMPPService service, int errorCode, string errorText)
      {
         string ownerJID = _JID(session.Login);
         string contactJID = _JID(xmppContact.JID);
         IEnumerable<NetworkOperation> operations = ownerJID == contactJID ?
                                                    FindOperation(ownerJID, null) :
                                                    FindOperation(contactJID, ownerJID);
         if (operations.Count() > 0)
         {
            foreach (NetworkOperation nwo in operations)
            {
               if (nwo.Name.Contains("REQUESTUSERINFO"))
               {
                  RemoveOperation(nwo);
                  OnRequestUserInfoState(this, new NetworkOperationEventArgs(nwo, OperationState.None, GetOperationFault(errorCode, errorText, "Request user info")));
                  break;
               }
            }
         }
         else if (!FNotLoggingErrors.Contains(errorCode))
         {
            FCommunicator.Log(GetOperationFault(errorCode, errorText, ""), false);
         }     
      }
      private void session_ContactStatusChange(wodXMPPComClass session, XMPPContact xmppContact, XMPPChatRoom chatRoom, StatusEnum newStatus, StatusEnum oldStatus)
      {
         //CoreTypes.EventLog.AddRecord("User \"" + xmppContact.Nick + "\" changed status - " + xmppContact.Subscription.ToString() + ", Status = " + xmppContact.Status.ToString(), true);

         if (xmppContact.Status == StatusEnum.Requested) return;

         Account account = GetAccountBySession(session);

         PresenceNotification pn = new PresenceNotification(TypeMap.MarshalStatus(newStatus),
                                   xmppContact.StatusText != xmppContact.Status.ToString() ? xmppContact.StatusText : "",
                                   new User(_JID(xmppContact.JID)), new User(_JID(session.Login)), account.ServiceConfig.Service);

         OnPresenceNotified(this, new MessagingOperationEventArgs(pn, null, OperationState.None, null));
      }

      #endregion
   }

   public enum SubscriptionAction
   {
      None,
      Auto,
      Deny,
      Repair,
      Remove,
      Subscribe,
      Unsubscribe,
   }

   internal static class TypeMap
   {
      internal static WODXMPPCOMLib.StatusEnum MarshalStatus(StatusCodes status)
      {
         switch (status)
         {
            case StatusCodes.Online: return StatusEnum.Online;
            case StatusCodes.SeemsAway: return StatusEnum.Away;
            case StatusCodes.Phantom: return StatusEnum.Invisible;
            case StatusCodes.Invisible: return StatusEnum.Invisible;
            case StatusCodes.NotAvailable: return StatusEnum.ExtendedAway;
            default: return StatusEnum.Offline;
         }
      }

      internal static StatusCodes MarshalStatus(StatusEnum xmppStatus)
      {
         switch (xmppStatus)
         {
            case StatusEnum.Chat:
            case StatusEnum.Online: return StatusCodes.Online;
            case StatusEnum.Away: return StatusCodes.SeemsAway;
            case StatusEnum.Invisible: return StatusCodes.Invisible;
            case StatusEnum.DoNotDisturb:
            case StatusEnum.ExtendedAway: return StatusCodes.NotAvailable;
            default: return StatusCodes.Offline;
         }
      }
   }

   internal class OleImageConverter
   {
      [DllImport("oleaut32.dll", EntryPoint = "OleCreatePictureIndirect", CharSet = CharSet.Ansi, ExactSpelling = true, PreserveSig = true)]
      private static extern int OleCreatePictureIndirect([In] PictDescBitmap pictdesc, ref Guid iid, bool fOwn,
          [MarshalAs(UnmanagedType.Interface)] out object ppVoid);

      const short _PictureTypeBitmap = 1;

      [StructLayout(LayoutKind.Sequential)]
      internal class PictDescBitmap
      {
         internal int cbSizeOfStruct = Marshal.SizeOf(typeof(PictDescBitmap));

         internal int pictureType = _PictureTypeBitmap;

         internal IntPtr hBitmap = IntPtr.Zero;

         internal IntPtr hPalette = IntPtr.Zero;

         internal int unused = 0;

         internal PictDescBitmap(Bitmap bitmap)
         {
            this.hBitmap = bitmap.GetHbitmap();
         }
      }

      public static stdole.IPictureDisp ImageToPictureDisp(Image image)
      {
         if (image == null || !(image is Bitmap))
         {
            return null;
         }

         PictDescBitmap pictDescBitmap = new PictDescBitmap((Bitmap)image);

         object ppVoid = null;

         Guid iPictureDispGuid = typeof(stdole.IPictureDisp).GUID;

         OleCreatePictureIndirect(pictDescBitmap, ref iPictureDispGuid, true, out ppVoid);

         stdole.IPictureDisp picture = (stdole.IPictureDisp)ppVoid;

         return picture;
      }

      public static Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
      {
         Image image = null;

         if (pictureDisp != null && pictureDisp.Type == _PictureTypeBitmap)
         {
            try
            {
               IntPtr paletteHandle = new IntPtr(pictureDisp.hPal);
               IntPtr bitmapHandle = new IntPtr(pictureDisp.Handle);
               image = Image.FromHbitmap(bitmapHandle, paletteHandle);
            }
            catch (Exception)
            {
               //
            }
         }

         return image;
      }
   }

   internal class XMPPServiceEntry
   {
      internal XMPPService Service;
      internal XMPPIdentity Identity;
      internal wodXMPPComClass Session;

      public override string ToString()
      {
         return "Service ID: " + Service.JID + "; Name: " + Identity.Name + "; Category: " + Identity.Category.ToString() + "; Type: " + Identity.Type;
      }
   }

   internal class XmppServiceConfig : ServiceConfig
   {
      string FDomain;
      short FPriority;
      string FResource;
      SecurityEnum FSecurity;
      AuthenticationsEnum FAuthentication;

      static string FDefaultResourceName = (CommunicationServiceObject._Communicator.AppName + "." + Environment.MachineName).ToLower();

      protected override void SetDefaultCapabilities()
      {
         if (FCapabilities == null) FCapabilities = new Dictionary<string, string>(); else FCapabilities.Clear();

         FCapabilities.Add("Features", StringExt.Join(new ServiceFeatures[]
         {
            ServiceFeatures.BlockList,
            ServiceFeatures.AddContact,
            ServiceFeatures.FindPerson,
            ServiceFeatures.PeopleSearch,
            ServiceFeatures.SearchByMask,

         }.Select(f => f.ToString()), " "));

         FCapabilities.Add("Actions", StringExt.Join(new ServiceActions[]
         {
            ServiceActions.SAVEINFO,
            ServiceActions.PASSWORD,

         }.Select(f => f.ToString()), " "));

         FCapabilities.Add("SearchBy", StringExt.Join(new ServiceFields[]
         {
            ServiceFields.Id,
            ServiceFields.Email,
            ServiceFields.Nickname,
            ServiceFields.FullName,
            ServiceFields.FirstName,
            ServiceFields.MiddleName,
            ServiceFields.LastName,
            ServiceFields.Birthday,
            ServiceFields.City,
            ServiceFields.Country,
            ServiceFields.Organization,
            ServiceFields.Department,

         }.Select(f => f.ToString()), " "));

         FCapabilities.Add("GettingOf", StringExt.Join(new ServiceFields[]
         {
            ServiceFields.Id,
            ServiceFields.Email,
            ServiceFields.Nickname,
            ServiceFields.FullName,
            ServiceFields.FirstName,
            ServiceFields.MiddleName,
            ServiceFields.LastName,
            ServiceFields.Birthday,
            ServiceFields.PersonalUrl,
            ServiceFields.Address,
            ServiceFields.CellPhone,
            ServiceFields.City,
            ServiceFields.Country,
            ServiceFields.Fax,
            ServiceFields.Phone,
            ServiceFields.State,
            ServiceFields.Zip,
            ServiceFields.Userpic,
            ServiceFields.Description,
            ServiceFields.Organization,
            ServiceFields.Department,
            ServiceFields.Role,
            ServiceFields.WorkAddress,
            ServiceFields.WorkCellPhone,
            ServiceFields.WorkCity,
            ServiceFields.WorkCountry,
            ServiceFields.WorkFax,
            ServiceFields.WorkPhone,
            ServiceFields.WorkState,
            ServiceFields.WorkZip,

         }.Select(f => f.ToString()), " "));

         FCapabilities.Add("SettingOf", StringExt.Join(new ServiceFields[]
         {
            ServiceFields.Email,
            ServiceFields.Nickname,
            ServiceFields.FullName,
            ServiceFields.FirstName,
            ServiceFields.MiddleName,
            ServiceFields.LastName,
            ServiceFields.Birthday,
            ServiceFields.PersonalUrl,
            ServiceFields.Address,
            ServiceFields.CellPhone,
            ServiceFields.City,
            ServiceFields.Country,
            ServiceFields.Fax,
            ServiceFields.Phone,
            ServiceFields.State,
            ServiceFields.Zip,
            ServiceFields.Userpic,
            ServiceFields.Description,
            ServiceFields.Organization,
            ServiceFields.Department,
            ServiceFields.Role,
            ServiceFields.WorkAddress,
            ServiceFields.WorkCellPhone,
            ServiceFields.WorkCity,
            ServiceFields.WorkCountry,
            ServiceFields.WorkFax,
            ServiceFields.WorkPhone,
            ServiceFields.WorkState,
            ServiceFields.WorkZip,

         }.Select(f => f.ToString()), " "));
      }

      internal XmppServiceConfig(ServiceInfo info)
         : base(info)
      {
         FPort = 5222;
         FPriority = 1;
         FResource = DefaultResourceName;
         FSecurity = SecurityEnum.SecurityAllowed;
         FAuthentication = AuthenticationsEnum.AuthAutomatic;

         SetDefaultCapabilities();
      }
      internal XmppServiceConfig(XElement xConfig, ServiceInfo info)
         : base(info)
      {
         FPort = 5222;
         FPriority = 1;
         FResource = DefaultResourceName;
         FSecurity = SecurityEnum.SecurityAllowed;
         FAuthentication = AuthenticationsEnum.AuthAutomatic;

         XObject = xConfig;

         if (FCapabilities == null) SetDefaultCapabilities();
      }
      internal XmppServiceConfig(XmppServiceConfig origin, bool userDefined)
         : base(origin, userDefined)
      {
         FPriority = origin.Priority;
         FResource = origin.Resource;
         FSecurity = origin.Security;
         FAuthentication = origin.Authentication;
      }

      public bool IsLocal
      {
         get
         {
            return FService.ServiceName == "Local Network";
         }
      }
      public string Domain
      {
         get
         {
            //return Dns.GetHostEntry(Environment.UserDomainName).HostName.ToLower();
            return IPGlobalProperties.GetIPGlobalProperties().DomainName.ToLower();
         }
         set
         {
            FDomain = value;
         }
      }
      public short Priority
      {
         get
         {
            return FPriority;
         }
         set
         {
            FPriority = value;
         }
      }
      public string Resource
      {
         get
         {
            return FResource;
         }
         set
         {
            FResource = value;
         }
      }
      public SecurityEnum Security
      {
         get
         {
            return FSecurity;
         }
         set
         {
            FSecurity = value;
         }
      }
      public AuthenticationsEnum Authentication
      {
         get
         {
            return FAuthentication;
         }
         set
         {
            FAuthentication = value;
         }
      }
      public static string DefaultResourceName
      {
         get
         {
            return FDefaultResourceName;
         }
      }

      public override XElement XObject
      {
         get
         {
            XElement x = base.XObject;

            if (FPort == 5222) x.SetAttributeValue("Port", null);
            if (!string.IsNullOrEmpty(FDomain)) x.SetAttributeValue("Domain", FDomain);
            if (FPriority != 1) x.SetAttributeValue("Priority", FPriority);
            if (FResource != DefaultResourceName) x.SetAttributeValue("Resource", FResource);
            if (FSecurity != SecurityEnum.SecurityAllowed) x.SetAttributeValue("Security", FSecurity.ToString());
            if (FAuthentication != AuthenticationsEnum.AuthAutomatic) x.SetAttributeValue("Authentication", FAuthentication.ToString());

            return x;
         }
         set
         {
            base.XObject = value;

            XAttribute domain = value.Attribute("Domain");
            XAttribute priority = value.Attribute("Priority");
            XAttribute resource = value.Attribute("Resource");
            XAttribute security = value.Attribute("Security");
            XAttribute authentication = value.Attribute("Authentication");
            if (authentication == null) authentication = value.Attribute("Auth");

            if (domain != null) FDomain = (string)domain;
            if (priority != null) FPriority = (short)priority;
            if (resource != null) FResource = (string)resource;
            if (security != null) FSecurity = (SecurityEnum)Enum.Parse(typeof(SecurityEnum), (string)security);
            if (authentication != null) FAuthentication = (AuthenticationsEnum)Enum.Parse(typeof(AuthenticationsEnum), (string)authentication);
         }
      }
      public override bool AuthCurrentUser
      {
         get
         {
            return Authentication == AuthenticationsEnum.AuthNTLMCurrentUser;
         }
      }
  }

   internal class NotSupportedException : ServiceOperationException
   {
      internal static string GetNotSupportedMessage(string feature)
      {
         return "This feature ('" + feature + "') is not supported by the currently installed XMPP driver.";
      }

      internal NotSupportedException(string feature)
         : base(ErrorReasonCode.NotSupported, GetNotSupportedMessage(feature))
      {
      }
   }

   internal enum XmppServiceFields
   {
      search,
      unknown,
      jid,     // Jabber ID
      user,    // Username
      username,
      fn,      // Full Name
      name,    // Full Name
      first,   // Name
      middle,  // Middle Name
      last,    // Family Name
      nick,    // Nickname
      nickname,
      bday,    // Birthday
      ctry,    // Country
      locality,// City
      email,   // Email
      orgname, // Organization Name
      orgunit, // Organization Unit
   }

   public static class UserExtension
   {
      public static bool IsGtalk(this User user)
      {
         return user.Identifier.Contains("@gmail.com") || user.Identifier.Contains("@googlemail.com");
      }
   }
}